using System;
using System.Net;
using System.Net.Sockets;
using __ts.Core.Proxy.Socks.Authentication;

namespace __ts.Core.Proxy.Socks
{

    ///<summary>Relays data between a remote host and a local client, using the SOCKS protocols.</summary>
    ///<remarks>This class implements the SOCKS4, SOCKS4a and SOCKS5 protocols.</remarks>
    ///<remarks>If the MustAuthenticate property is set, only SOCKS5 connections are allowed and the AuthList parameter of the constructor should not be null.</remarks>
    public sealed class SocksClient : Client
    {
        ///<summary>Initializes a new instance of the SocksClient class.</summary>
        ///<param name="ClientSocket">The Socket connection between this proxy server and the local client.</param>
        ///<param name="Destroyer">The method to be called when this SocksClient object disconnects from the local client and the remote server.</param>
        ///<param name="AuthList">The list with valid username/password combinations.</param>
        ///<remarks>If the AuthList is non-null, every client has to authenticate before he can use this proxy server to relay data. If it is null, the clients don't have to authenticate.</remarks>
        public SocksClient(Socket ClientSocket, DestroyDelegate Destroyer, AuthenticationList AuthList)
            : base(ClientSocket, Destroyer)
        {
            this.AuthList = AuthList;
        }
        ///<summary>Gets or sets the SOCKS handler to be used when communicating with the client.</summary>
        ///<value>The SocksHandler to be used when communicating with the client.</value>
        internal SocksHandler Handler
        {
            get
            {
                return m_Handler;
            }
            set
            {
                if (value == null)
                    throw new ArgumentNullException();
                m_Handler = value;
            }
        }
        ///<summary>Gets or sets the SOCKS handler to be used when communicating with the client.</summary>
        ///<value>The SocksHandler to be used when communicating with the client.</value>
        public bool MustAuthenticate
        {
            get
            {
                return m_MustAuthenticate;
            }
            set
            {
                m_MustAuthenticate = value;
            }
        }
        ///<summary>Starts communication with the client.</summary>
        public override void StartHandshake()
        {
            try
            {
                ClientSocket.BeginReceive(Buffer, 0, 1, SocketFlags.None, new AsyncCallback(this.OnStartSocksProtocol), ClientSocket);
            }
            catch
            {
                Dispose();
            }
        }
        ///<summary>Called when we have received some data from the client.</summary>
        ///<param name="ar">The result of the asynchronous operation.</param>
        private void OnStartSocksProtocol(IAsyncResult ar)
        {
            int Ret;
            try
            {
                Ret = ClientSocket.EndReceive(ar);
                if (Ret <= 0)
                {
                    Dispose();
                    return;
                }
                if (Buffer[0] == 4)
                { //SOCKS4 Protocol
                    if (MustAuthenticate)
                    {
                        Dispose();
                        return;
                    }
                    else
                    {
                        Handler = new Socks4Handler(ClientSocket, new NegotiationCompleteDelegate(this.OnEndSocksProtocol));
                    }
                }
                else if (Buffer[0] == 5)
                { //SOCKS5 Protocol
                    if (MustAuthenticate && AuthList == null)
                    {
                        Dispose();
                        return;
                    }
                    Handler = new Socks5Handler(ClientSocket, new NegotiationCompleteDelegate(this.OnEndSocksProtocol), AuthList);
                }
                else
                {
                    Dispose();
                    return;
                }
                Handler.StartNegotiating();
            }
            catch
            {
                Dispose();
            }
        }
        ///<summary>Called when the SOCKS protocol has ended. We can no start relaying data, if the SOCKS authentication was successful.</summary>
        ///<param name="Success">Specifies whether the SOCKS negotiation was successful or not.</param>
        ///<param name="Remote">The connection with the remote server.</param>
        private void OnEndSocksProtocol(bool Success, Socket Remote)
        {
            DestinationSocket = Remote;
            if (Success)
                StartRelay();
            else
                Dispose();
        }
        ///<summary>Gets or sets the AuthenticationList to use when a computer tries to authenticate on the proxy server.</summary>
        ///<value>An instance of the AuthenticationList class that contains all the valid username/password combinations.</value>
        private AuthenticationList AuthList
        {
            get
            {
                return m_AuthList;
            }
            set
            {
                m_AuthList = value;
            }
        }
        ///<summary>Returns text information about this SocksClient object.</summary>
        ///<returns>A string representing this SocksClient object.</returns>
        public override string ToString()
        {
            try
            {
                if (Handler != null)
                    return Handler.Username + " (" + ((IPEndPoint)ClientSocket.LocalEndPoint).Address.ToString() + ") connected to " + DestinationSocket.RemoteEndPoint.ToString();
                else
                    return "SOCKS connection from " + ((IPEndPoint)ClientSocket.LocalEndPoint).Address.ToString();
            }
            catch
            {
                return "Incoming SOCKS connection";
            }
        }
        // private variables
        /// <summary>Holds the value of the AuthList property.</summary>
        private AuthenticationList m_AuthList;
        /// <summary>Holds the value of the MustAuthenticate property.</summary>
        private bool m_MustAuthenticate = false;
        /// <summary>Holds the value of the Handler property.</summary>
        private SocksHandler m_Handler;
    }
}
